#include <crt_sem.h>
#include <crt_cond.h>
#include <crt_mutex.h>
#include <crt_barrier.h>


#if (TARGET_OS == OS_WINDOWS)
int uv_barrier_init(_barrier_t* barrier, unsigned int count) {

	int err;

	barrier->n = count;
	barrier->count = 0;

	err = _mutex_init(&barrier->mutex);
	if (err)
		return err;

	err = _sem_init(&barrier->turnstile1, 0);
	if (err)
		goto error2;

	err = _sem_init(&barrier->turnstile2, 1);
	if (err)
		goto error;

	return S_SUCCESS;

error:
	_sem_destroy(&barrier->turnstile1);
error2:
	_mutex_destroy(&barrier->mutex);

	return err;
}
void uv_barrier_destroy(_barrier_t* barrier) {

	_sem_destroy(&barrier->turnstile2);
	_sem_destroy(&barrier->turnstile1);
	_mutex_destroy(&barrier->mutex);
}
int uv_barrier_wait(_barrier_t* barrier) {

	int serial_thread;

	_mutex_lock(&barrier->mutex);
	if (++barrier->count == barrier->n) {
		_sem_wait(&barrier->turnstile2);
		_sem_post(&barrier->turnstile1);
	}
	_mutex_unlock(&barrier->mutex);

	_sem_wait(&barrier->turnstile1);
	_sem_post(&barrier->turnstile1);

	_mutex_lock(&barrier->mutex);
	serial_thread = (--barrier->count == 0);
	if (serial_thread) {
		_sem_wait(&barrier->turnstile1);
		_sem_post(&barrier->turnstile2);
	}
	_mutex_unlock(&barrier->mutex);

	_sem_wait(&barrier->turnstile2);
	_sem_post(&barrier->turnstile2);
	return serial_thread;

}

#elif (TARGET_OS == OS_POSIX)


int _barrier_init(_barrier_t* barrier, unsigned int count) {

	int rc = 0;
	barrier->in = 0;
	barrier->out = 0;
	barrier->threshold = count;

	rc = pthread_mutex_init(&barrier->mutex, NULL);
	if (rc != 0)
		goto error2;

	rc = pthread_cond_init(&barrier->cond, NULL);
	if (rc != 0)
		goto error;

	return S_SUCCESS;

error:
	pthread_mutex_destroy(&barrier->mutex);
error2:
	return rc;
	
}
void _barrier_destroy(_barrier_t* barrier) {

	pthread_mutex_lock(&barrier->mutex);

	while (barrier->out != 0)
		pthread_cond_wait(&barrier->cond, &barrier->mutex);

	if (barrier->in != 0)
		abort();

	pthread_mutex_unlock(&barrier->mutex);

	pthread_mutex_destroy(&barrier->mutex);
	pthread_cond_destroy(&barrier->cond);

}
int _barrier_wait(_barrier_t* barrier) {

	int last = 0;

	pthread_mutex_lock(&barrier->mutex);

	if (++barrier->in == barrier->threshold) {
		barrier->in = 0;
		barrier->out = barrier->threshold;
		pthread_cond_signal(&barrier->cond);
	}
	else {
		do {
			pthread_cond_wait(&barrier->cond, &barrier->mutex);
		} while (barrier->in != 0);
	}

	last = (--barrier->out == 0);
	pthread_cond_signal(&barrier->cond);

	pthread_mutex_unlock(&barrier->mutex);

	return last;
}

#elif (TARGET_OS == OS_DARWIN)

int _barrier_init(_barrier_t* barrier, unsigned int count) {

    int rc = 0;
    barrier->in = 0;
    barrier->out = 0;
    barrier->threshold = count;

    rc = pthread_mutex_init(&barrier->mutex, NULL);
    if (rc != 0)
        goto error2;

    rc = pthread_cond_init(&barrier->cond, NULL);
    if (rc != 0)
        goto error;

    return S_SUCCESS;

error:
    pthread_mutex_destroy(&barrier->mutex);
error2:
    return rc;
    
}
void _barrier_destroy(_barrier_t* barrier) {

    pthread_mutex_lock(&barrier->mutex);

    while (barrier->out != 0)
        pthread_cond_wait(&barrier->cond, &barrier->mutex);

    if (barrier->in != 0)
        abort();

    pthread_mutex_unlock(&barrier->mutex);

    pthread_mutex_destroy(&barrier->mutex);
    pthread_cond_destroy(&barrier->cond);

}
int _barrier_wait(_barrier_t* barrier) {

    int last = 0;

    pthread_mutex_lock(&barrier->mutex);

    if (++barrier->in == barrier->threshold) {
        barrier->in = 0;
        barrier->out = barrier->threshold;
        pthread_cond_signal(&barrier->cond);
    }
    else {
        do {
            pthread_cond_wait(&barrier->cond, &barrier->mutex);
        } while (barrier->in != 0);
    }

    last = (--barrier->out == 0);
    pthread_cond_signal(&barrier->cond);

    pthread_mutex_unlock(&barrier->mutex);

    return last;
}

#endif

